﻿using System;
using System.Collections.Generic;
using System.Threading;
using msilgc.common;
using msilgc.common.Attributes;

namespace SampleKernels {
    public class Sample1 {
        #region MSIL For Add_Arrays broken into basic blocks by hand
        /* Block #0:
             * 
             * 0x00: nop
             * 0x01: ldc.i4.0 
             * 0x02: stloc.0 
             * 0x03: br.s 0x10
             */

        /* Block #1
         * 
         * 0x05: nop 
         * 0x06: ldarg.2
         * 0x07: ldloc.0
         * 0x08: ldarg.0
         * 0x09: ldloc.0
         * 0x0a: ldelem.i4
         * 0x0b: ldarg.1
         * 0x0c: ldloc.0
         * 0x0d: ldelem.i4
         * 0x0e: add
         * 0x0f: stelem.i4
         * 0x10: nop
         * 0x11: ldloc.0
         * 0x12: ldc.i4.1
         * 0x13: add
         * 0x14: stloc.0
         */

        /* Block #2
         * 
         * 0x15: ldloc.0
         * 0x16: ldarg.0
         * 0x17: ldlen
         * 0x18: conv.i4
         * 0x19: clt
         * 0x1b: stloc.1
         * 0x1c: ldloc.1
         * 0x1d: brtrue.s 0xe6
         */

        /* Block #3
         * 
         * 0x1f: ret
         */
        #endregion


        public static void Add_Arrays(
            int[] a, int[] b, [OutParameter] int[] c) {

            for (int i = 0; i < a.Length; i++) {
                c[i] = a[i] + b[i];
            }
        }

        public static void Add_Arrays_Parallel(int[] a, int[] b, [OutParameter] int[] c, 
            [ThreadIdxParameter] Grain threadGrain, [BlockIdxParameter] Grain dataGrain) {
            c[threadGrain.X] = a[threadGrain.X] + b[threadGrain.X];
        }

        public static void TestPrimes_Parallel_EvenWorkload(
            [OutParameter] int[] divisorCount, int lowCount, 
            int dataGrain, int stepSize, [ThreadIdxParameter] Grain threadGrain) {

            int valuesTested = 0;
            for (int offsetToTest = threadGrain.X; valuesTested < dataGrain; offsetToTest += stepSize)
            {
                int numberToTest = lowCount + offsetToTest;
                int count = 0;
                for(int i = 2; i < numberToTest - 1; i++)
                {
                    if(numberToTest%i == 0)
                    {
                        count += 1;
                    }
                }
                divisorCount[offsetToTest] = count;
                valuesTested++;
            }
        }

        public static void TestPrimes_Parallel([OutParameter] int[] divisorCount, int lowCount, int dataGrain, [ThreadIdxParameter] Grain threadGrain)
        {
            int lowerBound = threadGrain.X*dataGrain;
            int upperBound = (threadGrain.X + 1) * dataGrain;
            for (int offsetToTest = lowerBound; offsetToTest < upperBound; offsetToTest++)
            {
                int numberToTest = lowCount + offsetToTest;
                int count = 0;
                for (int i = 2; i < numberToTest - 1; i++)
                {
                    if (numberToTest%i == 0)
                    {
                        count += 1;
                    }
                }
                divisorCount[offsetToTest] = count;
            }
        }

        public static void MatrixMultiplyHost(
            int[] a, int[] b, int[] c, int width)
        {
            for(int i = 0; i < width; i++)
            {
                for(int j = 0; j < width; j++)
                {
                    int sum = 0;
                    for(int k = 0; k < width; k++)
                    {
                        var x = a[i*width + k];
                        var y = b[k*width + j];
                        sum += x*y;
                    }
                    c[i*width + j] = sum;
                }
            }
        }

        public static void MatrixMultiplyHostMT(int[] a, int[] b, int[] c, int width)
        {
            int threadCount = Environment.ProcessorCount;
            List<Thread> threads = new List<Thread>();
            for(int i = 0; i < threadCount; i++)
            {
                Thread t = new Thread(MatrixMultiplyHostThread);
                t.Start(new MatrixArgs(a, b, c, width, i, threadCount));
                threads.Add(t);
            }
            foreach (Thread t in threads)
            {
                t.Join();
            }
        }

        private class MatrixArgs
        {
            public readonly int[] a;
            public readonly int[] b;
            public readonly int[] c;
            public readonly int width;
            public readonly int threadId;
            public readonly int threadCount;
            public MatrixArgs(int[] a, int[] b, int[] c, int width, int threadId, int threadCount)
            {
                this.a = a;
                this.b = b;
                this.c = c;
                this.width = width;
                this.threadId = threadId;
                this.threadCount = threadCount;
            }
        }

        private static void MatrixMultiplyHostThread(object args1)
        {
            MatrixArgs args = (MatrixArgs) args1;
            int minI = (args.width/args.threadCount)*args.threadId;
            int maxI = (args.width/args.threadCount)*(args.threadId+1);
            //Console.WriteLine("offset: " + minI + "," + maxI);
            int[] a = args.a;
            int[] b = args.b;
            int[] c = args.c;
            int width = args.width;
            for(int i = minI; i < maxI; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    int sum = 0;
                    for (int k = 0; k < width; k++)
                    {
                        var x = a[i * width + k];
                        var y = b[k * width + j];
                        sum += x * y;
                    }
                    c[i * width + j] = sum;
                }
            }
        }

        [CompileWithMsilgc]
        public static void MatrixMultiplyDevice(
            int[] a, int[] b, [OutParameter] int[] c, int width, int threadCount,
            [ThreadIdxParameter] Grain threadIdx,
            [BlockIdxParameter] Grain blockIdx,
            [BlockDimParameter] Grain blockDim)
        {
            int i = blockIdx.X*threadCount + threadIdx.X;
            int j = blockIdx.Y;
            int sum = 0;
            for(int k = 0; k < width; k++)
            {
                sum += a[i + width*k]*b[k + width*j];
            }
            c[i + width*j] = sum;
        }
    }
}
